package com.interview.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

/**
 * 统一认证、授权、限流拦截器
 * 实现三大功能：
 * 1. 请求频率限制：1分钟内同一主体最多10次请求
 * 2. 登录状态校验：未登录返回401
 * 3. 权限校验：无权限返回403
 *
 * @author 赵志伟
 * @date 2025-10-30
 */
public class AuthRateLimitInterceptor implements HandlerInterceptor {
    // 限流配置：1分钟窗口，最多10次请求
    private static final long RATE_LIMIT_WINDOW_MS = 60_000L;
    private static final int MAX_REQUESTS_PER_WINDOW = 10;

    //ArrayDeque：基于数组，性能较好，无容量限制（自动扩容）
    //LinkedList：基于链表，也实现了 Deque，但性能通常不如 ArrayDeque
    //代码使用了 ArrayDeque，这对频繁的头尾操作更高效。
    //总结在限流场景中，Deque 作为时间戳队列，能高效地在两端操作：头部清理过期记录，尾部添加新记录，适合实现滑动窗口限流。
    // 存储每个主体的请求时间戳队列
    // Key: 主体标识(登录用户用userId，未登录用IP)
    // Value: 请求时间戳队列
    private final Map<String, Deque<Long>> subjectRequestHistory = new ConcurrentHashMap<>();

    // JSON序列化工具
    private final ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 获取当前登录用户ID
     * 实际项目中可从JWT、Session、SecurityContext等获取
     *
     * 【Bearer Token 认证机制详解】
     *
     * 1. HTTP请求格式：
     *    Authorization: Bearer admin_token
     *    ↑              ↑       ↑
     *    请求头名     标准格式   token值
     *
     * 2. Bearer Token标准（RFC 6750）：
     *    - Bearer是OAuth 2.0的认证方案之一
     *    - 格式：Bearer <token>
     *    - 用于传递访问令牌（Access Token）
     *
     * 3. 认证流程：
     *    HTTP请求
     *      ↓
     *    拦截器 preHandle()
     *      ↓
     *    getLoginUserId() 提取Authorization请求头
     *      ↓
     *    检查是否以"Bearer "开头
     *      ↓
     *    提取token（去掉"Bearer "前缀，即substring(7)）
     *      ↓
     *    根据token识别用户身份
     *      ↓
     *    返回userId（或null表示未登录）
     *
     * 4. 当前实现的简化处理：
     *    - 真实JWT需要：解析、验证签名、检查过期时间
     *    - 这里简化：直接字符串匹配token值
     *    - 仅用于演示和测试
     *
     * 5. 两种认证方式优先级：
     *    - 方式1：Session认证（适合浏览器）
     *    - 方式2：Token认证（适合API、移动端）
     *    - Session优先，因为Session通常更安全（HttpOnly Cookie）
     */
    private String getLoginUserId(HttpServletRequest request) {
        // ========== 方式1: Session认证（适合浏览器场景）==========
        // 原理：服务器在登录时将userId存入Session，后续请求通过Session ID验证
        // 优点：更安全（HttpOnly Cookie），自动过期，服务端可控制
        // 缺点：需要Cookie支持，不适合移动端、跨域场景
        Object userId = request.getSession().getAttribute("LOGIN_USER_ID");
        if (userId != null) {
            return userId.toString();
        }

        // ========== 方式2: Bearer Token认证（适合API场景）==========
        // 原理：客户端在登录后获得token，后续请求在Authorization头中携带token
        // 优点：无状态、跨域友好、适合RESTful API、移动端
        // 缺点：需要客户端安全存储token、需要验证token有效性
        
        // 步骤1：从HTTP请求头获取Authorization字段
        // HTTP请求头示例：Authorization: Bearer admin_token
        String authHeader = request.getHeader("Authorization");
        
        // 步骤2：检查是否是Bearer Token格式
        // Bearer是OAuth 2.0标准定义的认证方案（RFC 6750）
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            
            // 步骤3：提取token值（去掉"Bearer "前缀）
            // "Bearer admin_token" → "admin_token"
            // substring(7) 因为 "Bearer " 长度为7
            String token = authHeader.substring(7);
            
            // 步骤4：根据token识别用户身份
            // 【注意】这里是简化实现，真实场景应该：
            // - 如果是JWT：解析JWT payload获取用户信息
            // - 如果是数据库token：查询数据库验证token有效性
            // - 验证token是否过期
            // - 验证token签名防止伪造
            
            // 模拟token映射（仅用于演示）
            if ("admin_token".equals(token)) {
                return "admin";
            }
            if ("user_token".equals(token)) {
                return "user001";
            }
            
            // 为测试添加更多token支持
            if ("test_token_unique".equals(token)) {
                return "test_user_unique";
            }
            if (token.startsWith("test_")) {
                // 为测试token生成唯一用户ID
                return "test_user_" + token.hashCode();
            }
        }

        // 未找到有效的认证信息，返回null表示未登录
        return null;
    }

    /**
     * 检查用户是否拥有指定权限
     * 实际项目中应查询数据库或缓存
     */
    private boolean hasPermission(String userId, String requiredPermission) {
        if (userId == null) {
            return false;
        }

        // 模拟权限数据
        Map<String, Set<String>> userPermissions = new HashMap<>();
        userPermissions.put("admin", new HashSet<>(Arrays.asList(
            "user:query", "user:add", "user:edit", "user:delete",
            "system:config", "system:monitor"
        )));
        userPermissions.put("user001", new HashSet<>(Arrays.asList(
            "user:query", "user:edit"
        )));

        Set<String> permissions = userPermissions.get(userId);
        return permissions != null && permissions.contains(requiredPermission);
    }

    /**
     * 获取客户端IP地址
     */
    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    /**
     * 检查是否触发限流
     * 使用滑动窗口算法
     */
    private boolean isRateLimited(String subjectKey) {
        long currentTime = System.currentTimeMillis();

        // 获取或创建该主体的请求历史队列
        Deque<Long> requestQueue = subjectRequestHistory.computeIfAbsent(
            subjectKey, k -> new ArrayDeque<>()
        );
        synchronized (requestQueue) {
            // 清理窗口外的旧请求记录
            // 如果第一个请求在1分钟之前，清理掉，反复循环
            while (!requestQueue.isEmpty() &&
                   currentTime - requestQueue.peekFirst() > RATE_LIMIT_WINDOW_MS) {
                requestQueue.pollFirst();
            }

            // 检查是否超过限制
            // 之前1分钟之外的请求都删除了, 所里这里是 1分钟之内的请求如果大于10次, 就触发限流
            if (requestQueue.size() >= MAX_REQUESTS_PER_WINDOW) {
                return true; // 触发限流
            }

            // 记录当前请求
            requestQueue.addLast(currentTime);
            return false;
        }
    }

    /**
     * 输出JSON格式的错误响应
     */
    private void writeJsonResponse(HttpServletResponse response, int httpStatus,
                                 int code, String message) {
        try {
            response.setStatus(httpStatus);
            response.setCharacterEncoding("UTF-8");
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);

            Map<String, Object> result = new HashMap<>();
            result.put("code", code);
            result.put("message", message);
            result.put("timestamp", System.currentTimeMillis());

            response.getWriter().write(objectMapper.writeValueAsString(result));
        } catch (Exception e) {
            // 记录日志，这里简化处理
            e.printStackTrace();
        }
    }

    //想要请求一个方法, 三步拦截
    //第一道拦截: 请求频率限制, 通过获取当前用户或当前ip的请求历史队列, 距今大于1分钟的都筛掉, 剩下的1分钟以内大于10个, 实施请求频率限制
    //第二道拦截: 如果没有登录, 拦截.
    //第三道拦截: 如果请求用户没有请求类或请求方法的权限, 拦截.
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                           Object handler) throws Exception {

        // ========== 步骤1：获取用户身份信息 ==========
        // Bearer Token认证机制在这里发挥作用：
        // 1. 如果请求包含 "Authorization: Bearer admin_token"
        // 2. getLoginUserId() 会提取并解析token
        // 3. 返回对应的userId（如 "admin"），表示已登录
        // 4. 如果没有token或token无效，返回null，表示未登录
        String userId = getLoginUserId(request);

        // ========== 步骤2：确定限流主体 ==========
        // Bearer Token的作用：让userId不为null，从而使用USER:userId作为限流主体
        // - 有Bearer Token（已登录）：subjectKey = "USER:admin"（基于用户限流）
        // - 无Bearer Token（未登录）：subjectKey = "IP:192.168.1.100"（基于IP限流）
        String subjectKey = (userId != null) ?
            ("USER:" + userId) : ("IP:" + getClientIp(request));

        // ========== 1. 请求频率限制校验 ==========
        if (isRateLimited(subjectKey)) {
            writeJsonResponse(response, 429, 429,
                "请求过于频繁，请稍后再试（1分钟内最多" + MAX_REQUESTS_PER_WINDOW + "次）");
            return false;
        }

        // ========== 2. 登录状态校验 ==========
        if (userId == null) {
            writeJsonResponse(response, 401, 401, "未登录或登录已过期，请先登录");
            return false;
        }

        // ========== 3. 权限校验 ==========
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;

            // 检查方法级别的权限注解
            RequiresPermission methodAnnotation =
                handlerMethod.getMethodAnnotation(RequiresPermission.class);

            // 检查类级别的权限注解
            RequiresPermission classAnnotation =
                handlerMethod.getBeanType().getAnnotation(RequiresPermission.class);

            // 确定所需权限（方法级别优先于类级别）
            // 方法有权限, 就用方法的; 没法没有设定权限, 那就看类有没有权限
            String requiredPermission = null;
            if (methodAnnotation != null) {
                requiredPermission = methodAnnotation.value();
            } else if (classAnnotation != null) {
                requiredPermission = classAnnotation.value();
            }

            // 如果需要权限且用户无权限，则拒绝访问
            if (requiredPermission != null && !hasPermission(userId, requiredPermission)) {
                writeJsonResponse(response, 403, 403,
                    "无权限访问该资源，需要权限：" + requiredPermission);
                return false;
            }
        }

        // 所有校验通过，允许继续处理请求
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                              Object handler, Exception ex) throws Exception {
        // 可以在这里记录访问日志、清理资源等
        if (ex != null) {
            System.err.println("请求处理异常: " + ex.getMessage());
        }
    }
}
